/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Microsoft.Scripting.Actions;

namespace Microsoft.Scripting.Ast {
    /// <summary>
    /// Base class for rewriting trees.  Subclasses can override individual Rewrite methods from which they can
    /// return rewritten nodes.  The TreeRewritter will handle the propagation up the tree of all the changed
    /// nodes.
    /// 
    /// Only nodes which are unchanged will be re-written.  To force processing of a node subclasses can call RewriteNode
    /// directly.
    /// 
    /// TODO: copy into Microsoft.Scripting, make internal
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Target = "TreeRewriter.TreeRewriter()")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Target = "TreeRewriter.TreeRewriter()")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:PoorMaintainability", Target="TreeRewriter.TreeRewriter()")]
    public partial class TreeRewriter {

        public Expression RewriteNode(Expression node) {
            if (node == null) {
                return null;
            }

            return _Rewriters[(int)node.NodeType](this, node);
        }

        protected virtual Expression RewriteExtension(Expression node) {
            return RewriteNode(Expression.ReduceToKnown(node));
        }

        // Helper to add a variable to a scope
        protected static Expression AddScopedVariable(Expression body, VariableExpression variable, Expression variableInit) {
            List<VariableExpression> vars = new List<VariableExpression>();
            string name = null;
            Annotations annotations = Annotations.Empty;
            while (body.NodeType == ExpressionType.Scope) {
                ScopeExpression scope = (ScopeExpression)body;
                vars.AddRange(scope.Variables);
                name = scope.Name;
                annotations = scope.Annotations;
                body = scope.Body;
            }

            vars.Add(variable);

            return Expression.Scope(
                Expression.Block(
                    Expression.Assign(variable, variableInit),
                    body
                ),
                name,
                annotations,
                vars
            );
        }

        #region Default re-write functions 

        private static Expression DefaultRewriteInvocationExpression(TreeRewriter rewriter, Expression n) {
            InvocationExpression node = (InvocationExpression)n;

            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression target = rewriter.RewriteNode(node.Expression);
                List<Expression> args = rewriter.RewriteArray(node.Arguments);
                if (target != node.Expression || args != null) {
                    return new InvocationExpression(
                        node.Annotations, 
                        target, 
                        node.Type, 
                        (CallAction)node.BindingInfo,
                        GetReadOnlyCollection(args, node.Arguments));
                }
            }

            return res;
        }

        private static Expression DefaultRewriteYieldStatement(TreeRewriter rewriter, Expression n) {
            YieldStatement node = (YieldStatement)n;

            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression val = rewriter.RewriteNode(node.Expression);
                if (val != node.Expression) {
                    res = new YieldStatement(node.Annotations, val);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteTryStatement(TreeRewriter rewriter, Expression n) {
            TryStatement node = (TryStatement)n;

            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression body = rewriter.RewriteNode(node.Body);
                List<CatchBlock> handlers = null;
                
                if (node.Handlers != null) {
                    for (int i = 0; i < node.Handlers.Count; i++) {
                        CatchBlock handler = node.Handlers[i];

                        CatchBlock block = rewriter.Rewrite(handler);
                        if (block == handler) {
                            // didn't rewrite the catch block, check what it's composed of...
                            Expression filter = rewriter.RewriteNode(handler.Filter);
                            Expression handlerBody = rewriter.RewriteNode(handler.Body);
                            VariableExpression var = (VariableExpression)rewriter.RewriteNode(handler.Variable);

                            if (filter != handler.Filter || handlerBody != handler.Body || var != handler.Variable) {
                                if (handlers == null) {
                                    handlers = new List<CatchBlock>();

                                    for (int j = 0; j < i; j++) {
                                        handlers[j] = node.Handlers[i];
                                    }
                                }

                                handlers.Add(new CatchBlock(handler.Annotations, handler.Test, var, handlerBody, filter));
                            } else if (handlers != null) {
                                handlers.Add(handler);
                            }
                        } else if (handlers != null) {
                            handlers.Add(handler);
                        }
                    }                    
                }

                Expression @finally = rewriter.RewriteNode(node.FinallyStatement);
                Expression fault = rewriter.RewriteNode(node.FaultStatement);

                if (body != node.Body ||
                    @finally != node.FinallyStatement ||
                    fault != node.FaultStatement || 
                    handlers != null) {
                    res = new TryStatement(node.Annotations, body, GetReadOnlyCollection(handlers, node.Handlers), @finally, fault);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteThrowStatement(TreeRewriter rewriter, Expression n) {
            ThrowStatement node = (ThrowStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression except = rewriter.RewriteNode(node.Exception);
                if (except != node.Exception) {
                    res = new ThrowStatement(node.Annotations, except);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteSwitchStatement(TreeRewriter rewriter, Expression n) {
            SwitchStatement node = (SwitchStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression test = rewriter.RewriteNode(node.TestValue);
                List<SwitchCase> newCases = null;

                for (int i = 0; i < node.Cases.Count; i++) {
                    SwitchCase sc = node.Cases[i];

                    SwitchCase newCase = rewriter.Rewrite(sc);

                    if (newCase == sc) {
                        Expression body = rewriter.RewriteNode(sc.Body);

                        if (body != sc.Body) {
                            newCase = new SwitchCase(sc.IsDefault, sc.Value, body);

                            if (newCases == null) {
                                newCases = new List<SwitchCase>();
                                for (int j = 0; j < i; j++) {
                                    newCases.Add(node.Cases[j]);
                                }
                            }

                            newCases.Add(newCase);
                        } else if (newCases != null) {
                            newCases.Add(sc);
                        }
                    } else if (newCases != null) {
                        newCases.Add(newCase);
                    }                
                }

                if (test != node.TestValue || newCases != null) {
                    res = new SwitchStatement(node.Annotations, node.Label, test, GetReadOnlyCollection(newCases, node.Cases));
                }
            }

            return res;
        }

        private static Expression DefaultRewriteScopeExpression(TreeRewriter rewriter, Expression n) {
            ScopeExpression node = (ScopeExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression body = rewriter.RewriteNode(node.Body);
                if (body != node.Body) {
                    res = new ScopeExpression(body, node.Name, node.Annotations, node.Variables);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteReturnStatement(TreeRewriter rewriter, Expression n) {
            ReturnStatement node = (ReturnStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression ret = rewriter.RewriteNode(node.Expression);
                if (ret != node.Expression) {
                    res = new ReturnStatement(node.Annotations, ret);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteNewArrayExpression(TreeRewriter rewriter, Expression n) {
            NewArrayExpression node = (NewArrayExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                List<Expression> exprs = rewriter.RewriteArray(node.Expressions);
                if (exprs != null) {
                    res = new NewArrayExpression(node.Annotations, node.NodeType, node.Type, new ReadOnlyCollection<Expression>(exprs));
                }
            }

            return res;
        }

        private static Expression DefaultRewriteMemberExpression(TreeRewriter rewriter, Expression n) {
            MemberExpression node = (MemberExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression target = rewriter.RewriteNode(node.Expression);
                if (target != node.Expression) {
                    res = new MemberExpression(node.Annotations, node.Member, target, node.Type, (MemberAction)node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteLoopStatement(TreeRewriter rewriter, Expression n) {
            LoopStatement node = (LoopStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression test = rewriter.RewriteNode(node.Test);
                Expression inc = rewriter.RewriteNode(node.Increment);
                Expression body = rewriter.RewriteNode(node.Body);
                Expression @else = rewriter.RewriteNode(node.ElseStatement);

                if (test != node.Test ||
                    inc != node.Increment ||
                    body != node.Body ||
                    @else != node.ElseStatement) {
                    res = new LoopStatement(node.Annotations, node.Label, test, inc, body, @else);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteLabeledStatement(TreeRewriter rewriter, Expression n) {
            LabeledStatement node = (LabeledStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression stmt = rewriter.RewriteNode(node.Statement);
                if (stmt != node.Statement) {
                    res = new LabeledStatement(node.Annotations, node.Label, stmt);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteDoStatement(TreeRewriter rewriter, Expression n) {
            DoStatement node = (DoStatement)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression test = rewriter.RewriteNode(node.Test);
                Expression body = rewriter.RewriteNode(node.Body);

                if (test != node.Test || node.Body != body) {
                    res = new DoStatement(node.Annotations, node.Label, test, body);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteDeleteExpression(TreeRewriter rewriter, Expression n) {
            DeleteExpression node = (DeleteExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression target = rewriter.RewriteNode(node.Expression);
                if (target != node.Expression) {
                    res = new DeleteExpression(node.Annotations, target, (DeleteMemberAction)node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteLambdaExpression(TreeRewriter rewriter, Expression n) {
            LambdaExpression node = (LambdaExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression body = rewriter.RewriteNode(node.Body);

                if (body != node.Body) {
                    res = new LambdaExpression(
                        node.Annotations,
                        node.NodeType,
                        node.Type,
                        node.Name,
                        node.ReturnType,
                        body,
                        node.Parameters,
                        node.ParameterArray
                    );
                }
            }

            return res;
        }

        private static Expression DefaultRewriteAssignmentExpression(TreeRewriter rewriter, Expression n) {
            AssignmentExpression node = (AssignmentExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression expr = rewriter.RewriteNode(node.Expression);
                Expression val = rewriter.RewriteNode(node.Value);

                if (expr != node.Expression || val != node.Value) {
                    res = new AssignmentExpression(node.Annotations, expr, val, node.Type, (DynamicAction)node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteBlock(TreeRewriter rewriter, Expression n) {
            Block node = (Block)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                List<Expression> nodes = rewriter.RewriteArray(node.Expressions);
                if (nodes != null) {
                    res = new Block(node.Annotations, new ReadOnlyCollection<Expression>(nodes), node.Type);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteActionExpression(TreeRewriter rewriter, Expression n) {
            ActionExpression node = (ActionExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                List<Expression> args = rewriter.RewriteArray(node.Arguments);
                if (args != null) {
                    res = new ActionExpression(node.Annotations, node.BindingInfo, new ReadOnlyCollection<Expression>(args), node.Type);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteTypeBinaryExpression(TreeRewriter rewriter, Expression n) {
            TypeBinaryExpression node = (TypeBinaryExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression op = rewriter.RewriteNode(node.Expression);
                if (op != node.Expression) {
                    res = new TypeBinaryExpression(node.Annotations, node.NodeType, op, node.Type);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteNewExpression(TreeRewriter rewriter, Expression n) {
            NewExpression node = (NewExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                List<Expression> args = rewriter.RewriteArray(node.Arguments);
                if (args != null) {
                    res = new NewExpression(node.Annotations, node.Type, node.Constructor, new ReadOnlyCollection<Expression>(args), (CreateInstanceAction)node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteUnaryExpression(TreeRewriter rewriter, Expression n) {
            UnaryExpression node = (UnaryExpression)n;
            Expression res = rewriter.Rewrite(node);
            
            if (res == node) {
                Expression op = rewriter.RewriteNode(node.Operand);
                if (op != node.Operand) {
                    res = new UnaryExpression(node.Annotations, node.NodeType, op, node.Type, node.Method, node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteConditionalExpression(TreeRewriter rewriter, Expression n) {
            ConditionalExpression node = (ConditionalExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression test = rewriter.RewriteNode(node.Test);
                Expression ifTrue = rewriter.RewriteNode(node.IfTrue);
                Expression ifFalse = rewriter.RewriteNode(node.IfFalse);

                if (test != node.Test ||
                    ifTrue != node.IfTrue ||
                    ifFalse != node.IfFalse) {
                    res = new ConditionalExpression(node.Annotations, test, ifTrue, ifFalse, node.Type);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteMethodCallExpression(TreeRewriter rewriter, Expression n) {
            MethodCallExpression node = (MethodCallExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression inst = rewriter.RewriteNode(node.Object);
                List<Expression> newArgs = rewriter.RewriteArray(node.Arguments);

                if (newArgs != null || inst != node.Object) {
                    res = new MethodCallExpression(
                        node.Annotations,
                        node.Type,
                        (InvokeMemberAction)node.BindingInfo,
                        node.Method,
                        inst,
                        GetReadOnlyCollection(newArgs, node.Arguments)
                    );
                }
            }
            return res;
        }

        private static Expression DefaultRewriteBinaryExpression(TreeRewriter rewriter, Expression n) {
            BinaryExpression node = (BinaryExpression)n;
            Expression res = rewriter.Rewrite(node);

            if (res == node) {
                Expression left = rewriter.RewriteNode(node.Left);
                Expression right = rewriter.RewriteNode(node.Right);
                Expression conversion = rewriter.RewriteNode(node.Conversion);

                if (left != node.Left || right != node.Right || conversion != node.Conversion) {
                    res = new BinaryExpression(node.Annotations,
                        node.NodeType,
                        left,
                        right,
                        node.Type,
                        node.Method,
                        (LambdaExpression)conversion,
                        node.BindingInfo);
                }
            }

            return res;
        }

        private static Expression DefaultRewriteExtensionExpression(TreeRewriter rewriter, Expression n) {
            return rewriter.RewriteExtension(n);
        }

        #region Stateless nodes

        // these nodes are all nodes w/o children nodes.  

        private static Expression DefaultRewriteEmptyStatement(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((EmptyStatement)node);
        }

        private static Expression DefaultRewriteContinueStatement(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((ContinueStatement)node);
        }

        private static Expression DefaultRewriteLocalScopeExpression(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((LocalScopeExpression)node);
        }

        private static Expression DefaultRewriteBreakStatement(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((BreakStatement)node);
        }

        private static Expression DefaultRewriteParameterExpression(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((ParameterExpression)node);
        }

        private static Expression DefaultRewriteVariableExpression(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((VariableExpression)node);
        }

        private static Expression DefaultRewriteConstantExpression(TreeRewriter rewriter, Expression node) {
            return rewriter.Rewrite((ConstantExpression)node);
        }

        #endregion

        #endregion

        #region Private implementation helpers

        /// <summary>
        /// Helper to re-write an array of arguments.  Returns null if none of the
        /// children are rewritten or the new array containing both re-written and
        /// non re-written nodes.
        /// </summary>
        private List<Expression> RewriteArray(IList<Expression> args) {
            List<Expression> newArgs = null;
            if (args != null) {
                for (int i = 0; i < args.Count; i++) {
                    Expression curNode = args[i];
                    Expression newNode = RewriteNode(curNode);
                    if (newNode != curNode || newArgs != null) {
                        if (newArgs == null) {
                            newArgs = new List<Expression>();
                            for (int j = 0; j < i; j++) {
                                newArgs.Add(args[j]);
                            }
                        }

                        newArgs.Add(newNode);
                    }
                }
            }
            return newArgs;
        }

        private static ReadOnlyCollection<T> GetReadOnlyCollection<T>(List<T> newList, ReadOnlyCollection<T> oldList) {
            if (newList == null) {
                return oldList;
            }

            return new ReadOnlyCollection<T>(newList);
        }

        #endregion
    }
}
